package net.amsoft.iservice.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlException;
import org.openxri.resolve.Resolver;
import org.openxri.resolve.TrustType;
import org.openxri.resolve.exception.PartialResolutionException;
import org.openxri.xml.SEPElement;
import org.openxri.xml.SEPUri;
import org.openxri.xml.Service;
import org.openxri.xml.Tags;
import org.openxri.xml.XRD;

import x0Metadata.oasisNamesTcSAML2.EndpointType;
import x0Metadata.oasisNamesTcSAML2.EntityDescriptorDocument;
import x0Metadata.oasisNamesTcSAML2.EntityDescriptorType;
import x0Metadata.oasisNamesTcSAML2.IDPSSODescriptorType;
import x0Metadata.oasisNamesTcSAML2.IndexedEndpointType;
import x0Metadata.oasisNamesTcSAML2.SPSSODescriptorType;

import net.amsoft.iservice.util.MetaData;
import net.amsoft.iservice.util.IServiceConstants;

/**
 * @author mohit
 */
public final class ResolverClient {

    private static Logger logger = Logger.getLogger(ResolverClient.class);

    // data argument variable
    private static String msTargetXRI;

    // option variables
    private static boolean mbIsVerbose;
    private static boolean mbDontOutputHeader;
    private static String msRootEqualsURI;
    private static String msRootAtURI;
    private static String msRootBangURI;
    private static TrustType trustType = new TrustType();
    private static boolean followRefs = true;
    private static Resolver resolver = null;
    final private static String ROOT_DEF_EQ_URI = IServiceConstants.ROOT_DEF_EQ_URI;
    final private static String ROOT_DEF_AT_URI = IServiceConstants.ROOT_DEF_AT_URI;
    final private static String ROOT_DEF_BANG_URI = IServiceConstants.ROOT_DEF_BANG_URI;

    static {
        // re-initialize variables so this may be called more than once
        StringBuffer sOutput = new StringBuffer();
        msTargetXRI = null;
        mbIsVerbose = false;
        mbDontOutputHeader = false;
        msRootEqualsURI = ROOT_DEF_EQ_URI;
        msRootAtURI = ROOT_DEF_AT_URI;
        msRootBangURI = ROOT_DEF_BANG_URI;

        // create and configure a resolver
        resolver = new Resolver();

        XRD eqRoot = new XRD();
        Service eqAuthService = new Service();
        eqAuthService.addMediaType(Tags.CONTENT_TYPE_XRDS + ";trust=none",
                SEPElement.MATCH_ATTR_CONTENT, Boolean.FALSE);
        eqAuthService.addType(Tags.SERVICE_AUTH_RES);
        eqAuthService.addURI(msRootEqualsURI);
        eqRoot.addService(eqAuthService);

        XRD atRoot = new XRD();
        Service atAuthService = new Service();
        atAuthService.addMediaType(Tags.CONTENT_TYPE_XRDS + ";trust=none",
                SEPElement.MATCH_ATTR_CONTENT, Boolean.FALSE);
        atAuthService.addType(Tags.SERVICE_AUTH_RES);
        atAuthService.addURI(msRootAtURI);
        atRoot.addService(atAuthService);

        XRD bangRoot = new XRD();
        Service bangAuthService = new Service();
        bangAuthService.addMediaType(Tags.CONTENT_TYPE_XRDS + ";trust=none",
                SEPElement.MATCH_ATTR_CONTENT, Boolean.FALSE);
        bangAuthService.addType(Tags.SERVICE_AUTH_RES);
        bangAuthService.addURI(msRootBangURI);
        bangRoot.addService(bangAuthService);

        resolver.setAuthority("=", eqRoot);
        resolver.setAuthority("@", atRoot);
        resolver.setAuthority("!", bangRoot);

        // invoke the tracert
        // lookup(sOutput, resolver);
    }

    /**
     * Makes a <b>HTTP Get </b> request to the address of Meta data service and
     * reads the Meta data XML redered
     * 
     * @param metaDataServiceUri
     *            Web address of MetaData service
     * @return Meta Data xml string
     */
    public static String getMetaDataXML(String metaDataServiceUri) {
        logger.info("getMetaDataXML() metaDataServiceUri: "
                + metaDataServiceUri);

        URL resolution;
        URLConnection resolutionCon = null;
        BufferedReader in;
        String inputLine;
        StringBuffer xridXml = new StringBuffer();

        try {
            resolution = new URL(metaDataServiceUri);
            resolutionCon = resolution.openConnection();

            // Cast to a HttpURLConnection
            if (resolutionCon instanceof HttpURLConnection) {
                HttpURLConnection httpConnection = (HttpURLConnection) resolutionCon;
                int code = httpConnection.getResponseCode();
                logger.debug("getMetaDataXML() response code: " + code);

                if (code > 399 && code < 500)
                    return null;
            } else {
                logger.error("error - not a http request!");
            }

            in = new BufferedReader(new InputStreamReader(resolutionCon
                    .getInputStream()));

            logger.info("getMetaDataXML() Starting to read metadata....");

            while ((inputLine = in.readLine()) != null) {
                xridXml.append(inputLine);
            }

            in.close();
        } catch (MalformedURLException e) {
            logger.warn("getMetaDataXML() Exception: " + e.getMessage());
        } catch (IOException ioe) {
            logger.warn("getMetaDataXML() Exception: " + ioe.getMessage());
        }

        logger.info("getMetaDataXML() Exit.");
        return xridXml.toString();
    }

    /**
     * Gets all the atribute values of MetaData, bundles, and returns its object
     * 
     * @param identifier
     *            Identifier for which Login/Logout action is to be performed
     * @param forIDP
     *            true if identifier belongs to IDP, otherwise if its an <BR>
     *            SP then forIDP will have false value
     * 
     * @return MetaData object
     */
    public static final MetaData getFinalMetaData(String identifier) {
        logger.info("getFinalMetaData() Entry with iname=" + identifier);

        try {
            XRD authXrd = resolver.resolveAuthToXRD(identifier, trustType,
                    followRefs);

            Service oService = authXrd
                    .getFirstServiceByType(IServiceConstants.SERVICE_AUTHENTICATION);
            String providerID = oService.getProviderID();
            String globalINumber = authXrd.getCanonicalidAt(0).getValue()
                    .toLowerCase();

            if (!providerID.startsWith(IServiceConstants.XRI_SCHEME)) {
                providerID = IServiceConstants.XRI_SCHEME + providerID;
            }

            // Construct Qxri to with the metadata
            String sQXRI = providerID + "/"
                    + IServiceConstants.SERVICE_AUTHENTICATION_PATH + "/"
                    + IServiceConstants.SERVICE_METADATA;

            String metaDataServiceUri = resolver.resolveSEPToTextURIList(sQXRI,
                    trustType, null, null, followRefs);
            metaDataServiceUri = metaDataServiceUri.split("\n")[0];
            String metaDataXML = getMetaDataXML(metaDataServiceUri);
            MetaData oMetaData = createIDPMetaData(metaDataXML);

            oMetaData.setResolvedFor(identifier);
            oMetaData.setGlobalInumber(globalINumber);
            oMetaData.setProviderID(providerID);
            oMetaData.setServiceEndPoint(metaDataServiceUri);

            logger.info("getFinalMetaData() Returning MetaData");
            return oMetaData;
        } catch (Exception e) {
            logger.error("getFinalMetaData(): identifier: " + identifier
                    + ", could not resolved");
            return null;
        }

    }// end of getMetaData()

    public static MetaData getSPMetaData(String spXri) {
        try {

            // Construct Qxri to with the metadata
            String sQXRI = spXri;
            if (spXri.endsWith("/"))
                sQXRI += IServiceConstants.SERVICE_METADATA;
            else
                sQXRI += "/" + IServiceConstants.SERVICE_METADATA;

            String metaDataServiceUri = resolver.resolveSEPToTextURIList(sQXRI,
                    trustType, null, null, followRefs);
            metaDataServiceUri = metaDataServiceUri.split("\n")[0];
            String metaDataXML = getMetaDataXML(metaDataServiceUri);
            MetaData oMetaData = null;
            oMetaData = createSPMetaData(metaDataXML);
            oMetaData.setResolvedFor(spXri);
            return oMetaData;
        } catch (Exception e) {
            return null;
        }

    }

    /**
     * Resolves
     * 
     * @param idpXri
     * @return
     */
    public static MetaData getIDPMetaData(String idpXri) {
        try {
            String sQXRI = idpXri;
            if (idpXri.endsWith("/"))
                sQXRI += IServiceConstants.SERVICE_METADATA;
            else
                sQXRI += "/" + IServiceConstants.SERVICE_METADATA;

            String metaDataServiceUri = resolver.resolveSEPToTextURIList(sQXRI,
                    trustType, null, null, followRefs);

            metaDataServiceUri = metaDataServiceUri.split("\n")[0];
            String metaDataXML = getMetaDataXML(metaDataServiceUri);
            MetaData oMetaData = null;
            oMetaData = createIDPMetaData(metaDataXML);
            oMetaData.setResolvedFor(idpXri);
            return oMetaData;

        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Parses the metadata xml for an IDP into xml bean object and retrieves the
     * URI of the services
     * 
     * @param metaDataXML
     *            XML String of metadata, which is to be parsed into obect
     * @return MetaData object <BR>
     *         following values are set in MetaData object <BR>
     *         ISSOUrl <BR>
     *         ISLOUrl <BR>
     *         ArtifactResolutionServiceURL <BR>
     *         else will be null
     */
    public static MetaData createIDPMetaData(String metaDataXML) {
        IDPSSODescriptorType descriptorType = getMetaDataObject(metaDataXML);
        // Get Artifact url
        IndexedEndpointType indexedEndpointType = descriptorType
                .getArtifactResolutionServiceArray(0);
        String artifactResolutionServiceURL = indexedEndpointType.getLocation();
        logger.debug("getIDPMetaData() artifactResolutionServiceURL: "
                + artifactResolutionServiceURL);
        // Get SSO url
        EndpointType endpointTypeSSO = descriptorType
                .getSingleSignOnServiceArray(0);
        String issoSep = endpointTypeSSO.getLocation();
        logger.debug("getIDPMetaData() issoSep: " + issoSep);
        // Get SLO url
        EndpointType endpointTypeSLO = descriptorType
                .getSingleLogoutServiceArray(0);
        String isloSep = endpointTypeSLO.getLocation();
        logger.debug("getIDPMetaData() isloSep: " + isloSep);
        MetaData metaData = new MetaData();
        metaData.setISSOUrl(issoSep);
        metaData.setISLOUrl(isloSep);
        metaData.setArtifactResolutionServiceURL(artifactResolutionServiceURL);
        return metaData;
    }
    /**
     * @param metaDataXML
     * @return MetaData object containing: <BR>
     *         ISLOUrl <BR>
     *         AssertionConsumerServiceURL <BR>
     *         other element will have default null value
     */
    public static MetaData createSPMetaData(String metaDataXML) {
        SPSSODescriptorType descriptorType = getSpSSODescriptor(metaDataXML);
        // Get Artifact url
        IndexedEndpointType indexedEndpointType = descriptorType
                .getAssertionConsumerServiceArray(0);
        String assertionConsumerServiceURL = indexedEndpointType.getLocation();
        logger.debug("getSPMetaData() artifactResolutionServiceURL: "
                + assertionConsumerServiceURL);
        // Get SLO url
        EndpointType endpointTypeSLO = descriptorType
                .getSingleLogoutServiceArray(0);
        String isloSep = endpointTypeSLO.getLocation();
        logger.debug("getSPMetaData() isloSep: " + isloSep);
        MetaData metaData = new MetaData();
        metaData.setISLOUrl(isloSep);
        metaData.setAssertionConsumerServiceURL(assertionConsumerServiceURL);
        return metaData;
    }

    /**
     * @param metaDataXML
     * @return
     */
    private static SPSSODescriptorType getSpSSODescriptor(String metaDataXML) {
        logger.info("getSpSSODescriptor(): Entry with metaDataXML: "
                + metaDataXML);
        EntityDescriptorDocument entityDescriptorDocument = null;
        try {
            entityDescriptorDocument = EntityDescriptorDocument.Factory
                    .parse(metaDataXML);
        } catch (XmlException e) {
            logger
                    .error("getSpSSODescriptor(): Could not parse Metadata XML into object, "
                            + e.getMessage());
        }
        EntityDescriptorType entityDescriptorType = entityDescriptorDocument
                .getEntityDescriptor();
        SPSSODescriptorType descriptorType = entityDescriptorType
                .getSPSSODescriptorArray(0);
        logger.info("getSpSSODescriptor(): Returning MetaDataXMLBean Object");
        return descriptorType;
    }

    /**
     * Parses the Meta data xml into an xml bean object
     * 
     * @param metaDataXML
     *            XML String of Meta data
     * @return Meta Data object
     */
    private static IDPSSODescriptorType getMetaDataObject(String metaDataXML) {
        logger.info("getMetaDataObject() Entry with metaDataXML: "
                + metaDataXML);
        EntityDescriptorDocument entityDescriptorDocument = null;
        try {
            entityDescriptorDocument = EntityDescriptorDocument.Factory
                    .parse(metaDataXML);
        } catch (XmlException e) {
            logger
                    .error("getMetaDataObject(): Could not parse Metadata XML into object, "
                            + e.getMessage());
        }

        EntityDescriptorType entityDescriptorType = entityDescriptorDocument
                .getEntityDescriptor();
        IDPSSODescriptorType descriptorType = entityDescriptorType
                .getIDPSSODescriptorArray(0);

        logger.info("getMetaDataObject(): Returning MetaDataXMLBean Object");
        return descriptorType;
    }

    /**
     * Resolves the Iname and extracts its Global I Number provided as first
     * Cannonical ID in the final XRD
     * 
     * @param sIname
     *            Iname of whoose Global INumber is to be extracted
     * @return Global INumber
     */
    public static String getGlobalINumber(String sIname) {
        try {
            XRD oXRD = resolver.resolveAuthToXRD(sIname, trustType, followRefs);
            String sGINU = oXRD.getCanonicalidAt(0).getValue();

            if (sGINU != null)
                return sGINU.toLowerCase();
            else
                return null;
        } catch (PartialResolutionException e) {
            return null;
        }
    }

    public static MetaData getMetaDataIDP(String metaDataServiceUri) {
        String metaDataXML = getMetaDataXML(metaDataServiceUri);
        MetaData oMetaData = createIDPMetaData(metaDataXML);
        return oMetaData;
    }

    /**
     * This method extracts the Uri for server where OpenId Service of the
     * Identifier is subscribed to. <BR>
     * First, an https uri is searched, if not available the https uri is
     * selected
     * 
     * @param sIdentifier
     *            I-name or I-number
     * @return URL of the OpenId server OR null if not subscribed to
     * @throws PartialResolutionException
     *             Thrown when identifier could not be succesfully resolved
     */
    public static String getOpenIdServerUri(String sIdentifier)
             {
        XRD authXrd = null;
        try {
            authXrd = resolver.resolveAuthToXRD(sIdentifier, trustType,
                    followRefs);
        } catch (PartialResolutionException e) {
            return null;
        }

        Service oService = authXrd
                .getFirstServiceByType(IServiceConstants.OPENDID_AUTHENTICATION);
        
        String sOpenIdServerUri = null;
        if(oService != null){
            SEPUri oSep = oService.getURIForScheme("http");
            sOpenIdServerUri = oSep.getURI().toString();
        }

        //TODO Janrain OpenId library does not support HTTPS. Should be uncommebted
        // when library starts to support HTTPS
        
        // get uri with http if https uri not available
        // if (oSep == null)
        // sOpenIdServerUri = oService.getURIForScheme("http").getURI()
        // .toString();
        // else
        // sOpenIdServerUri =oSep.getURI().toString();

        return sOpenIdServerUri;
    }
    
    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
    private final void writeObject(ObjectOutputStream out)
            throws java.io.IOException {
        throw new java.io.IOException("Object cannot be serialized");
    }

}